﻿' ******************************************************************************
' ** 
' **  Yahoo Finance Managed
' **  Written by Marius Häusler 2010
' **  It would be pleasant, if you contact me when you are using this code.
' **  Contact: YahooFinanceManaged@gmail.com
' **  Project Home: http://code.google.com/p/yahoo-finance-managed/
' **  
' ******************************************************************************
' **  
' **  Copyright 2010 Marius Häusler
' **  
' **  Licensed under the Apache License, Version 2.0 (the "License");
' **  you may not use this file except in compliance with the License.
' **  You may obtain a copy of the License at
' **  
' **    http://www.apache.org/licenses/LICENSE-2.0
' **  
' **  Unless required by applicable law or agreed to in writing, software
' **  distributed under the License is distributed on an "AS IS" BASIS,
' **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' **  See the License for the specific language governing permissions and
' **  limitations under the License.
' ** 
' ******************************************************************************


Namespace Finance.Support

    ''' <summary>
    ''' Class for managing stock exchange information. Serializable. 
    ''' </summary>
    ''' <remarks></remarks>
    <Serializable()> _
    Public Class StockExchange
        Private mExchangeID As String = String.Empty
        Private mName As String = String.Empty
        Private mSuffix As String = String.Empty
        Private mCountry As Country = Country.AR
        Private mCurrency As Currency = Finance.Currency.AED
        Private mDelayMinutes, mRelativeToUTC, mTradingHours, mTradingMinutes As Integer
        Private mOpeningTimeUTC, mClosingTimeUTC, mOpeningTimeLocalExchange As DateTime
        Private mTradingDays As New List(Of DayOfWeek)
        Private mHolidays As New List(Of Date)

        ''' <summary>
        ''' The ID of the exchange
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>If the ID is in WorldMarket.DefaultStockExchanges, properties will be setted automatically</remarks>
        Public ReadOnly Property ID() As String
            Get
                Return mExchangeID
            End Get
        End Property
        ''' <summary>
        ''' The ending string for stock IDs
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>If the suffix is in DefaultStockExchanges, properties will get automatically</remarks>
        Public ReadOnly Property Suffix() As String
            Get
                Return mSuffix
            End Get
        End Property

        ''' <summary>
        ''' The name of the exchange
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Name() As String
            Get
                Return mName
            End Get
            Set(ByVal value As String)
                mName = value
            End Set
        End Property
        ''' <summary>
        ''' The country of the exchange
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Country() As Country
            Get
                Return mCountry
            End Get
            Set(ByVal value As Country)
                mCountry = value
            End Set
        End Property
        ''' <summary>
        ''' The currency of the country
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Currency() As Currency
            Get
                Return mCurrency
            End Get
            Set(ByVal value As Currency)
                mCurrency = value
            End Set
        End Property

        ''' <summary>
        ''' The days when trading is active
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property TradingDays() As List(Of DayOfWeek)
            Get
                Return mTradingDays
            End Get
            Set(ByVal value As List(Of DayOfWeek))
                mTradingDays = value
            End Set
        End Property
        ''' <summary>
        ''' Days without active trading time.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Holidays() As List(Of Date)
            Get
                Return mHolidays
            End Get
            Set(ByVal value As List(Of Date))
                mHolidays = value
            End Set
        End Property
        ''' <summary>
        ''' The data response delay to realtime of yahoo servers
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property DelayMinutes() As Integer
            Get
                Return mDelayMinutes
            End Get
            Set(ByVal value As Integer)
                If value > 0 And value < 3600 Then mDelayMinutes = value
            End Set
        End Property
        ''' <summary>
        ''' The number of hours relative to UTC of the exchange's timezone
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property RelativeToUTC() As Integer
            Get
                Return mRelativeToUTC
            End Get
            Set(ByVal value As Integer)
                If value >= -12 And value <= 12 Then
                    mRelativeToUTC = value
                    Me.SetTradingTimes()
                End If
            End Set
        End Property
        ''' <summary>
        ''' The time when trading starts
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>By setting a value, the date is not important, only hour and minute</remarks>
        Public Property OpeningTimeUTC() As DateTime
            Get
                Return mOpeningTimeUTC
            End Get
            Set(ByVal value As DateTime)
                mOpeningTimeUTC = #12:00:00 AM#.AddHours(value.Hour).AddMinutes(value.Minute)
                Me.SetTradingTimes()
            End Set
        End Property
        ''' <summary>
        ''' The time when trading ends
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks>By setting a value, the date is not important, only hour and minute. If time value is smaler than opening, trading ends on the next day. 24 hours trading is maximum</remarks>
        Public Property ClosingTimeUTC() As DateTime
            Get
                Return mClosingTimeUTC
            End Get
            Set(ByVal value As DateTime)
                mClosingTimeUTC = #12:00:00 AM#.AddHours(value.Hour).AddMinutes(value.Minute)
                Me.SetTradingTimes()
            End Set
        End Property
        ''' <summary>
        ''' The local time of exchange's timezone when trading starts
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property OpeningTimeLocalExchange() As DateTime
            Get
                Return mOpeningTimeLocalExchange
            End Get
        End Property

        ''' <summary>
        ''' Default constructor
        ''' </summary>
        ''' <param name="id"></param>
        ''' <param name="suffix"></param>
        ''' <remarks></remarks>
        Public Sub New(ByVal id As String, ByVal suffix As String)
            Me.Clear()
            Dim setted As Boolean = False
            Dim suff As String = suffix.ToUpper
            Dim m As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(suffix, "\.[a-zA-Z0-9][a-zA-Z0-9]?$")
            If m.Success Then suff = m.Value.ToUpper

            If WorldMarket.DefaultStockExchanges IsNot Nothing Then
                For Each se As StockExchange In WorldMarket.DefaultStockExchanges
                    If se IsNot Nothing AndAlso se.ID.ToUpper = id.ToUpper Then
                        Me.CopyValues(se)
                        setted = True
                        Exit For
                    End If
                Next
                If Not setted Then
                    For Each se As StockExchange In WorldMarket.DefaultStockExchanges
                        If suffix <> String.Empty And suff = se.Suffix Then
                            Me.CopyValues(se)
                            setted = True
                            Exit For
                        End If
                    Next
                End If
            End If
            If Not setted Then
                mExchangeID = id.ToUpper
                If mSuffix = String.Empty Then mSuffix = suff
            End If
            mTradingDays.AddRange(New DayOfWeek() {DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday})
        End Sub
        ''' <summary>
        ''' Overloaded constructor
        ''' </summary>
        ''' <param name="id">The ID of the stock exchange</param>
        ''' <param name="suffix">The suffix of the stock exchange ID</param>
        ''' <param name="name">The name of the stock exchange</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal id As String, ByVal suffix As String, ByVal name As String)
            Me.New(id, suffix)
            mName = name
        End Sub
        Friend Sub New(ByVal se As StockExchange)
            If se Is Nothing Then Throw New ArgumentNullException("se", "Original StockExchange is null.")
            Me.CopyValues(se)
        End Sub

        ''' <summary>
        ''' Clears all values.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub Clear()
            mName = String.Empty
            mCountry = Country.US
            mCurrency = Currency.AED
            mTradingHours = 24
            mRelativeToUTC = 0
            mOpeningTimeUTC = #12:00:00 AM#
            mClosingTimeUTC = #12:00:00 AM#
            mDelayMinutes = 0
            mTradingDays = New List(Of DayOfWeek)
            Me.SetTradingTimes()
        End Sub
        ''' <summary>
        ''' Returns if trading is active at a specific datetime in relation to machine's current timezone.
        ''' </summary>
        ''' <param name="atThisLocalTime">The result will be in relation to the passed DateTime of the machine's local time zone.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function IsActiveLocal(ByVal atThisLocalTime As DateTime) As Boolean
            Return IsActiveUTC(TimeZone.CurrentTimeZone.ToUniversalTime(atThisLocalTime))
        End Function
        ''' <summary>
        ''' Returns if trading is active at a specific DateTime in relation to UTC datetime.
        ''' </summary>
        ''' <param name="atThisUTCTime">The result will be in relation to the passed DateTime of UTC time zone.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function IsActiveUTC(ByVal atThisUTCTime As DateTime) As Boolean
            Dim timeZoneDate As DateTime = atThisUTCTime.AddHours(mRelativeToUTC)
            Dim tradingSpan As New TimeSpan(mTradingHours, mTradingMinutes, 0)
            Dim holiday As Boolean = False
            If mHolidays IsNot Nothing Then
                For Each h As Date In mHolidays
                    If h.Date = timeZoneDate.Date Then
                        holiday = True
                        Exit For
                    End If
                Next
            End If
            If Not holiday Then
                If mTradingDays.Contains(timeZoneDate.DayOfWeek) Then
                    If IsInSpan(timeZoneDate, tradingSpan) Then
                        Return True
                    Else
                        Return IsInSpan(timeZoneDate, tradingSpan, -1)
                    End If
                Else
                    Return ((mOpeningTimeUTC.Hour + mTradingHours + ((mOpeningTimeUTC.Minute + mTradingMinutes) / 60) > 24 And _
                             mTradingDays.Contains(timeZoneDate.AddDays(-1).DayOfWeek)) AndAlso _
                             IsInSpan(timeZoneDate, tradingSpan, -1))
                End If
            Else
                Return False
            End If
        End Function

        Private Function IsInSpan(ByVal d As DateTime, ByVal ts As TimeSpan, Optional ByVal relativeDays As Integer = 0) As Boolean
            Dim newD As DateTime = d.AddMinutes(mDelayMinutes * -1)
            With newD.AddDays(relativeDays)
                If mTradingDays IsNot Nothing AndAlso mTradingDays.Contains(.DayOfWeek) Then
                    Dim startDate As DateTime = .Date.AddHours(mOpeningTimeLocalExchange.Hour).AddMinutes(mOpeningTimeLocalExchange.Minute)
                    Dim endDate As DateTime = startDate.Add(ts)
                    Return (newD >= startDate And newD <= endDate)
                Else
                    Return False
                End If
            End With
        End Function
        Private Sub SetTradingTimes()
            If mOpeningTimeUTC = mClosingTimeUTC Then
                mTradingHours = 24
                mTradingMinutes = 0
            ElseIf mOpeningTimeUTC < mClosingTimeUTC Then
                Dim ts As TimeSpan = mClosingTimeUTC - mOpeningTimeUTC
                mTradingHours = CInt(Math.Truncate(ts.TotalHours))
                mTradingMinutes = CInt((ts.TotalHours Mod 1) * 60)
            Else
                Dim endDate As DateTime = mClosingTimeUTC.AddDays(1)
                Dim ts As TimeSpan = endDate - mOpeningTimeUTC
                mTradingHours = CInt(Math.Truncate(ts.TotalHours))
                mTradingMinutes = CInt((ts.TotalHours Mod 1) * 60)
            End If

            Dim diff As Double = (mOpeningTimeUTC.Hour * 60 + mOpeningTimeUTC.Minute) + (mRelativeToUTC * 60)
            If diff < 0 Then
                mOpeningTimeLocalExchange = #12:00:00 AM#.AddDays(1).AddMinutes(diff)
            Else
                Dim open As Date = mOpeningTimeUTC.AddHours(mRelativeToUTC)
                mOpeningTimeLocalExchange = #12:00:00 AM#.AddHours(open.Hour).AddMinutes(open.Minute)
            End If
        End Sub
        Private Sub CopyValues(ByVal se As StockExchange)
            If se IsNot Nothing Then
                mExchangeID = se.ID
                mCountry = se.Country
                mSuffix = se.Suffix
                mName = se.Name
                mCountry = se.Country
                mCurrency = se.Currency
                mTradingDays.Clear()
                mTradingDays.AddRange(se.TradingDays)
                mDelayMinutes = se.DelayMinutes
                mRelativeToUTC = se.RelativeToUTC
                mOpeningTimeUTC = se.OpeningTimeUTC
                mClosingTimeUTC = se.ClosingTimeUTC
                Me.SetTradingTimes()
            End If
        End Sub

        ''' <summary>
        ''' Returns the name of the stock exchange
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function ToString() As String
            Return mName
        End Function

    End Class

End Namespace